package com.ruoyi.business.service.impl.loan;

import com.alibaba.fastjson.JSONObject;
import com.ruoyi.bizsys.domain.*;
import com.ruoyi.bizsys.dto.UserInfo;
import com.ruoyi.bizsys.service.*;
import com.ruoyi.bizsys.utils.UserCacheService;
import com.ruoyi.business.Vo.RulePremiseVo;
import com.ruoyi.business.common.riskRule.RulePremiseService;
import com.ruoyi.business.common.riskRule.RuleStrategy;
import com.ruoyi.business.domain.*;
import com.ruoyi.business.mapper.LoanMapper;
import com.ruoyi.business.service.*;
import com.ruoyi.bizsys.utils.BizConstants;
import com.ruoyi.common.enums.RspEnmu;
import com.ruoyi.common.exception.BusinessException;
import com.ruoyi.common.utils.*;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.system.service.ISysDictDataService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author: Jamnsin
 * @Date: 2021/3/10 6:04 下午
 */
@Slf4j
@Service
@AllArgsConstructor
public class LoanApplyServiceImpl implements ILoanApplyService {

    private IProductInfoService productInfoService;

    private IBLoanInfoService bLoanInfoService;

    private ILoanOrderService loanOrderService;

    private LoanMapper loanMapper;

    private ILoanIouService loanIouService;

    private IAppService appService;

    private ICustBankCardService custBankCardService;

    private RulePremiseService premiseService;

    private ICreditAccountService creditAccountService;

    private ISysDictDataService dictDataService;

    private UserCacheService userCacheService;


    @Override
    public void processLoanReq(LoanReq loanReq, boolean isVersion228) {
        log.info("loanReq:{},isVersion228{}", loanReq, isVersion228);
        UserInfo userInfo = userCacheService.getUserCacheInfo();
        //贷前检查
        preLoanInspection(userInfo.getCustNo(), loanReq.getLoanSerialNo());
        //是否默认产品
        loanReq.setProductCode(getProductCode(loanReq.getProductCode()));
        //查询产品信息
        ProductInfo product = productInfoService.getOneByProductCode(loanReq.getProductCode());
        //提现金额是否在产品额度及可用额度范围内
        loanAmtCheck(loanReq.getLoanAmt(), product, userInfo);
        //保存设备信息
        appService.saveAppDeviceInfo(userInfo.getUserId(), userInfo.getCustNo(), BizConstants.ACTION_TYPE_LOAN, loanReq.getLoanSerialNo(), loanReq.getDeviceInfo());
        //贷款初始化 生成提现订单、冻结额度
        loanOrderInitializationBoard(loanReq, userInfo, product);

        //风控规则校验 区分228前后版本
        if (isVersion228) {
            rulePremiseV228(loanReq, userInfo);
        } else {
            rulePremise(loanReq, userInfo);
        }
    }

    /**
     * 提现订单初始化，类内部必须调用这个方法事务才会生效
     *
     * @param loanReq
     * @param userInfo
     * @param product
     */
    private void loanOrderInitializationBoard(LoanReq loanReq, UserInfo userInfo, ProductInfo product) {
        LoanApplyServiceImpl bean = SpringUtils.getBean(LoanApplyServiceImpl.class);
        bean.loanOrderInitialization(loanReq, userInfo, product);
    }

    @Transactional(rollbackFor = Exception.class)
    public void loanOrderInitialization(LoanReq loanReq, UserInfo userInfo, ProductInfo product) {
        // 生成提现订单
        generateLoanOrder(loanReq, product, userInfo.getCustNo());
        //更新账户额度，冻结提现额度
        // TODO: 2022/4/26 update判断余额是否充足
        int i = creditAccountService.updateFrozenQuota(userInfo.getCustNo(), loanReq.getLoanAmt());
//         int i = loanMapper.updatefrozenQuota(userInfo.getCustNo(), loanReq.getLoanAmt());
        if (i == 0) {
            throw new BusinessException(RspEnmu.CREDIT_LINE_AVAILABLE_QUOTA_INSUFFICIENT);
        }
    }

    /**
     * 贷前检查
     *
     * @param custNo
     * @param loanSerialNo
     */
    private void preLoanInspection(String custNo, String loanSerialNo) {
        //本地一笔在贷
        LimitOneLoanOrderCheck(custNo);
        //订单检查
        duplicateOrderCheck(loanSerialNo);
        //冻结期检查
        freezePeriodCheck(custNo);
        // 逾期不能提现
        overdueCheck(custNo);
        //当日提现失败3次，不能再发起提现
        failCountCheckOfToday(custNo);
        //在途订单检查
        orderInTransitCheck(custNo);
    }

    private void rulePremise(LoanReq loanReq, UserInfo userInfo) {
        String fingerprint = Optional.ofNullable(loanReq.getDeviceInfo()).orElse(new JSONObject()).getString("fingerprint");
        RulePremiseVo rulePremiseVo = RulePremiseVo.builder().custNo(userInfo.getCustNo()).userId(userInfo.getUserId()).type(RuleStrategy.LOAN).serialNo(loanReq.getLoanSerialNo()).fingerprint(fingerprint).build();
        premiseService.rulePremise(rulePremiseVo, "orderFlag", "deviceFlag");
    }

    private void rulePremiseV228(LoanReq loanReq, UserInfo userInfo) {
        String newFingerprint = Optional.ofNullable(loanReq.getDeviceInfo()).orElse(new JSONObject()).getString("newFingerprint");
        RulePremiseVo rulePremiseVo = RulePremiseVo.builder().custNo(userInfo.getCustNo()).type(RuleStrategy.LOAN).serialNo(loanReq.getLoanSerialNo()).userId(userInfo.getUserId()).fingerprint(newFingerprint).build();
        premiseService.rulePremiseV228(rulePremiseVo);
    }

    /**
     * 检查更新产品code
     *
     * @param productCode
     * @return
     */
    private String getProductCode(String productCode) {
        return StringUtils.equals(productCode, "0000") ? getSuitableProduct() : productCode;
    }

    /**
     * 一笔在贷
     *
     * @param custNo
     */
    private void LimitOneLoanOrderCheck(String custNo) {
        if (bLoanInfoService.isLoanInfoListByInTransit(custNo)) {
            throw new BusinessException(RspEnmu.LOAN_APPLY_IS_ALREADY_ON_THE_WAY2);
        }
    }

    /**
     * 订单是否已存在
     *
     * @param loanSerialNo
     */
    private void duplicateOrderCheck(String loanSerialNo) {
        if (loanOrderService.countByLoanSerialNo(loanSerialNo) > 0) {
            throw new BusinessException(RspEnmu.SERIAL_NO_DUPLICATE);
        }
    }

    /**
     * 逾期不能提现
     *
     * @param custNo
     */
    private void overdueCheck(String custNo) {
        boolean isOverdue = loanIouService.getListByCustNo(custNo).stream().anyMatch(p -> p.getIouStatus() == 2);
        if (isOverdue) {
            throw new BusinessException(RspEnmu.NOT_LOAN_BEFORE_REPAY_OVERDUE_LOAN);
        }
    }

    /**
     * 当日提现失败3次，不能再发起提现
     *
     * @param custNo
     */
    private void failCountCheckOfToday(String custNo) {
        int count = loanOrderService.todayCountByStatus(custNo, LoanOrder.LOAN_STATUS_5_LOAN_FAILED, LoanOrder.LOAN_STATUS_8_CLOSE);
        if (count > 2) {
            throw new BusinessException(RspEnmu.REACHED_UPPER_LIMIT);
        }
    }

    private boolean isBindCard(String userId) {
        return custBankCardService.countByUserId(userId) > 0;
    }

    /**
     * 是否有Processing，OutStanding的订单
     *
     * @param custNo
     */
    private void orderInTransitCheck(String custNo) {
        if (bLoanInfoService.isLoanInfoListByInTransit(custNo)) {
            throw new BusinessException(RspEnmu.LOAN_APPLY_IS_ALREADY_ON_THE_WAY);
        }
    }

    /**
     * 提现冻结期检查
     *
     * @param custNo
     */
    private void freezePeriodCheck(String custNo) {
        //提现风控拒绝7天惩罚期，7天内不能提现
        LoanOrder loanOrder = loanOrderService.getOneByCustNo(custNo);
        if (Func.isNotEmpty(loanOrder) && loanOrder.getLoanStatus() == LoanOrder.LOAN_STATUS_0_APPROVE_REFUSED) {
            freezePeriodCheck(loanOrder);
        }
    }

    /**
     * 提现冻结期检查
     *
     * @param loanOrder
     */
    private void freezePeriodCheck(LoanOrder loanOrder) {
        String freezeDate = loanOrder.getFreezeDate();
        int freezeDay = Integer.valueOf(Optional.ofNullable(freezeDate).orElse("7"));
        //是否已过冻结期
        if (Func.dayDifference(loanOrder.getCreateTime()) < freezeDay) {
            Map<Object, Object> hashMap = new HashMap<>();
            hashMap.put("freezeDay", freezeDate);
            throw new BusinessException(RspEnmu.NOT_LOAN_WITHIN_7.getMessage(), hashMap);
        }
    }

    @Override
    public CustAccount getCustAccount() {
        return loanMapper.getCustAccount(userCacheService.getUserId());
    }

    @Override
    public List<CustLoanIou> getCustNotSettledIou() {
        return loanMapper.getCustNotSettledIou();
    }

    private String getSuitableProduct() {
        return loanMapper.getSuitableProduct();
    }

    private void loanAmtCheck(BigDecimal loanAmt, ProductInfo product, UserInfo userInfo) {
        CreditAccount creditAccount = creditAccountService.getByCustNo(userInfo.getCustNo());
        BigDecimal minQuota = product.getMinQuota();
        BigDecimal maxQuota = product.getMaxQuota();
        BigDecimal creditScale = product.getCreditScale();
        BigDecimal productCreditQuota = creditAccount.getCreditLine().multiply(creditScale);
        BigDecimal availableQuota = loanMapper.getAvailableQuota(creditAccount.getCustNo());

        log.info("loanAmt = {}, minQuota = {}, maxQuota = {}, creditScale = {}, productCreditQuota = {}, availableQuota = {}", loanAmt, minQuota, maxQuota, creditScale, productCreditQuota, availableQuota);

        if (loanAmt.doubleValue() <= 0 || loanAmt.doubleValue() < minQuota.doubleValue()) {
            throw new BusinessException(RspEnmu.LOWER_THAN_PRODUCT_MIN_QUOTA);
        }

        if (loanAmt.doubleValue() > productCreditQuota.doubleValue() || loanAmt.doubleValue() > maxQuota.doubleValue()) {
            throw new BusinessException(RspEnmu.MORE_THAN_PRODUCT_QUOTA);
        }

        if (loanAmt.doubleValue() > availableQuota.doubleValue()) {
            throw new BusinessException(RspEnmu.CREDIT_LINE_AVAILABLE_QUOTA_INSUFFICIENT);
        }

        //未绑卡
        if (!isBindCard(userInfo.getUserId())) {
            quotaRatioCheck(loanAmt, creditAccount);
        }
        //可用额度是否满足
        availableQuotaCheck(loanAmt, creditAccount);
    }

    /**
     * 可用额度是否满足
     *
     * @param Quota
     * @param creditAccount
     */
    private void availableQuotaCheck(BigDecimal Quota, CreditAccount creditAccount) {
        List<LoanIou> loanIouList = loanIouService.getListByCustNo(creditAccount.getCustNo());
        if (!CollectionUtils.isEmpty(loanIouList)) {
            List<LoanIou> noSettleLoanIous = loanIouList.stream().filter(p -> p.getIouStatus() != 1).collect(Collectors.toList());
            BigDecimal onlineQuota = noSettleLoanIous.stream().map(LoanIou::getLoanPrincipal).reduce(BigDecimal.ZERO, BigDecimal::add);
            //在贷本金+冻结期 = 总在贷金额
            BigDecimal totalOnlineQuota = onlineQuota.add(creditAccount.getFrozenQuota());
            totalOnlineQuota = totalOnlineQuota.doubleValue() < 0 ? BigDecimal.ZERO : totalOnlineQuota;
            //授信额度 - 总在贷金额 = 可用金额
            BigDecimal dbAvailableQuota = creditAccount.getCreditLine().subtract(totalOnlineQuota);
            //借款金额 是否大于 可用金额  ｜｜ 授信金额 小于 在贷金额
            if (Quota.doubleValue() > dbAvailableQuota.doubleValue() || creditAccount.getCreditLine().doubleValue() < totalOnlineQuota.doubleValue()) {
                throw new BusinessException(RspEnmu.CREDIT_LINE_AVAILABLE_QUOTA_INSUFFICIENT);
            }
        }
    }


    private void quotaRatioCheck(BigDecimal loanAmt, CreditAccount creditAccount) {
        BigDecimal loanQuotaRatio = BigDecimal.valueOf(Double.valueOf(dictDataService.selectDictLabel("loan_settings", "loan_quota_ratio")));
        BigDecimal withdrawalLimit = creditAccount.getCreditLine().multiply(loanQuotaRatio).subtract(creditAccount.getLoanQuota()).subtract(creditAccount.getFrozenQuota());
        withdrawalLimit = withdrawalLimit.doubleValue() < 0 ? BigDecimal.ZERO : withdrawalLimit;
        log.info("loanQuotaRatio = {}, withdrawalLimit = {}", loanQuotaRatio, withdrawalLimit);
        if (loanAmt.doubleValue() > withdrawalLimit.doubleValue()) {
            throw new BusinessException(RspEnmu.CREDIT_LINE_AVAILABLE_QUOTA_INSUFFICIENT);
        }
    }

    /**
     * 初始化提现申请订单
     *
     * @param loanReq
     * @param product
     * @param custNo
     * @return
     */
    private LoanOrder generateLoanOrder(LoanReq loanReq, ProductInfo product, String custNo) {
        LoanOrder loanOrder = new LoanOrder();
        loanOrder.setCustNo(custNo);
        loanOrder.setAccountNumber(loanReq.getAccountNumber());
        loanOrder.setProductCode(loanReq.getProductCode());
        loanOrder.setLoanAmt(loanReq.getLoanAmt());
        loanOrder.setDateLateFee(product.getDateLateFee());
        loanOrder.setLoanPurpose(loanReq.getLoanPurpose());
        loanOrder.setLoanPurposeOther(loanReq.getLoanPurposeOther());
        loanOrder.setLoanSerialNo(loanReq.getLoanSerialNo());
        loanOrder.setLoanStatus(LoanOrder.LOAN_STATUS_9_APPROVALING);
        loanOrder.setRepayDate(DateUtils.getFewDay(product.getPeriodNum()));

        BigDecimal serviceFee = loanReq.getLoanAmt().multiply(product.getServiceFee());
        loanOrder.setServiceFee(serviceFee);
        loanOrder.setServiceFeeRate(product.getServiceFee());
        loanOrder.setLateFeeMaxDays(product.getLateFeeMaxDays());

        loanOrder.setFkProductId(product.getId());
        loanOrder.setInterestRate(product.getInterestRate());
        loanOrder.setMaxQuota(product.getMaxQuota());
        loanOrder.setMinQuota(product.getMinQuota());
        loanOrder.setIsSinglePayment(product.getIsSinglePayment());
        loanOrder.setLateFeeGracePeriod(product.getLateFeeGracePeriod());
        loanOrder.setPeriodNum(product.getPeriodNum());
        loanOrder.setProductName(product.getProductName());
        loanOrder.setUnit(product.getUnit());

        BigDecimal interestAmt = loanReq.getLoanAmt().multiply(product.getInterestRate()).multiply(BigDecimal.valueOf(product.getPeriodNum()));
        loanOrder.setInterestAmt(interestAmt);

        loanOrder.setTotalAmt(loanReq.getLoanAmt().add(interestAmt).add(serviceFee));

        loanOrderService.insertLoanOrder(loanOrder);
        return loanOrder;
    }

    @Override
    public List<RaiseLimitTaskVo> getCustNotSettledIou0_3() {
        return loanMapper.getCustNotSettledIou0_3();
    }

}
